__author__ = 'xiaosu'

import pexpect
import logging
from models import Hosts
from octopus import db,cache
import re
import time,threading
from multiprocessing import Pool,cpu_count,Manager
import os
basedir = os.path.abspath(os.path.dirname(__file__))



class new_host(object):


    def __init__(self, count_dict,add_token ,hostname,username,password,clusterid,audit,enabled,comment,serverport='22',rootpassword=""):

        self.count_dict=count_dict
        self.add_token=add_token
        self.hostname = hostname
        self.username = username
        self.password = password
        self.clusterid = clusterid
        self.audit = audit
        self.enabled = enabled
        self.comment = comment
        self.serverport = serverport
        self.rootpassword = rootpassword
        self.logger=logging.getLogger("octopus")
        self.logger.setLevel(logging.INFO)
        self.fh=logging.FileHandler(os.path.join(basedir,'./log/add_hosts_log/')+self.hostname+'-'+self.username+'.log')
        self.fh.setFormatter(logging.Formatter('%(asctime)s    %(levelname)s  %(message)s'))
        self.fh.setLevel(logging.INFO)
        self.logger.addHandler(self.fh)
        self.result=""




    def open(self):

        ssh_newkey = 'Are you sure you want to continue connecting'
        self._child = pexpect.spawn('ssh -o "NumberOfPasswordPrompts=1"  -o "RSAAuthentication=no" -o "PubkeyAuthentication=no" -p %s -l %s %s '%(self.serverport,self.username, self.hostname))
        self.logger.info('ssh -o "NumberOfPasswordPrompts=1"  -o "RSAAuthentication=no" -o "PubkeyAuthentication=no" -p %s -l %s %s '%(self.serverport,self.username, self.hostname))

        i = self._child.expect([pexpect.TIMEOUT, 'Connection refused', ssh_newkey, 'password: ',pexpect.EOF])
        if i == 0 or i == 4 :
            # print 'ERROR!'
            # print 'SSH could not login. Here is what SSH said:'
            # print self._child.before, self._child.after
            self.logger.error("pexpect timeout, maybe no route to host "+self.hostname)
            self.result = "no route to host ! Please check network !"

            return
        if i == 1:
            # print 'Host is unreachable,maybe server is down '
            # print 'SSH could not login. Here is what SSH said:'
            # print self._child.before, self._child.after
            self.logger.error("Connection refused, maybe "+self.hostname+"'s ssh port is wrong !")
            self.result = "Connection refused, maybe "+self.hostname+" ssh port is wrong  or because of Firewall "
            return

        if i == 2: # SSH does not have the public key. Just accept it.
            self._child.sendline ('yes')
            #print self._child.before, self._child.after
            self.logger.info("accept server's public key")
            i = self._child.expect([pexpect.TIMEOUT, 'password: '])
            if i == 0: # Timeout
                # print 'ERROR!'
                # print 'SSH could not login. Here is what SSH said:'
                # print self._child.before, self._child.after
                self.logger.error("pexpect timeout,server doesn't response after accept the public key")
                self.result = "server did not response after accept the public key "
                return

        self._child.sendline(self.password)
        p=self._child.expect([pexpect.TIMEOUT,'Permission denied', '\$'])
#        p=self._child.expect([pexpect.TIMEOUT,'Permission denied', '\$','\#'])
        if p == 0:
            #print 'password wrong'
            self.logger.error("timeout after sending password")
            self.result = "timeout after sending password "
            return
        if p == 1:
            self.logger.error(self.username+"' password is wrong")
            self.result = self.username+" password is wrong"
            return
        if p == 2:
            self.logger.info(self.hostname+" "+self.username+"'s password is OK")
            if self.rootpassword != "":
                self._child.sendline("export LANG=c")
                self._child.sendline("su - root")
                self._child.expect("assword:")
                self._child.sendline(self.rootpassword)
                r=self._child.expect(["\#","Authentication failure","incorrect password"])
                if r == 0 :
                    self.logger.info(self.hostname+" root's password is OK")
                    self._child.sendline("exit")
                    self._child.sendline("exit")
                    self.result = "checked successfully"
                if r == 1 or r ==2:
                    self.logger.info(self.hostname+"root's password is incorrect")
                    self.result = self.hostname+" root password is incorrect"
                    self._child.sendline("exit")
            else:
                self._child.sendline("exit")
                self.result = "checked successfully"
            self.logger.info("this is EOF")
            return
        # if p == 3:
        #     self._child.sendline("exit")
        #     self.logger.info(self.hostname+"added success")
        #     self.logger.info("this is root")
        #     return

    def __del__(self):
        self.logger.removeHandler(self.fh)
        if self.result == "checked successfully":
            self.count_dict["check_ok_"+self.add_token]+=1
        else:
            self.count_dict["check_fail_"+self.add_token]+=1





def ip_to_list(ipaddress):
    """
    parse string to IP address Pool
    """
    ip_pool=[]
    ip= re.compile("^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])(\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])){2}\.")
    ip_group = ip.sub("",ipaddress)
    tmp = ip.match(ipaddress)
    prefix=tmp.group(0)
    single_check=re.split('[,-]',ip_group)
    single_re=re.compile("^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$")
    for i in single_check:
        if single_re.match(i) == None:
            return "invalid IP address"
    iplist=ip_group.split(",")
    for i in iplist:
        try:
            n=i.split("-")
            first=int(n[0])
            last=int(n[1])
            for ip in xrange(first,last+1):
                ip_pool.append(prefix+str(ip))
        except Exception as e:
            if str(e) == "list index out of range":
                ip_pool.append(prefix+str(i))
    return ip_pool




class multi_add(object):


    def __init__(self,ip_pool,username,password,clusterid,audit,enabled,comment,serverport,rootpassword,direct,add_token):

        self.ip_pool=ip_pool
        self.username=username
        self.password=password
        self.clusterid=clusterid
        self.audit=audit
        self.enabled=enabled
        self.comment=comment
        self.serverport=serverport
        self.rootpassword=rootpassword
        self.direct=direct
        self.new_pool=[]
        self.result=[]
        self.add_token=add_token
        self.check_ok="check_ok_"+add_token
        self.check_fail="check_fail_"+add_token
        self.check_remain="check_remain_"+add_token
        for i in ip_pool:
            self.new_pool.append([i,self])

    def get_count(self):

        check_fail_tmp=0
        check_ok_tmp=0
        while check_fail_tmp+check_ok_tmp != len(self.ip_pool):
            interval=0.15
            ok=self.count_dict[self.check_ok]
            fail=self.count_dict[self.check_fail]
            try:
                cache.set(self.check_ok,ok)
                cache.set(self.check_fail,fail)
                cache.set(self.check_remain,len(self.ip_pool)-ok-fail)
            except Exception as e:
                pass
            time.sleep(interval)
            check_ok_tmp=cache.get(self.check_ok)
            if check_ok_tmp == None :
                check_ok_tmp =0
            check_fail_tmp=cache.get(self.check_fail)
            if check_fail_tmp == None:
                check_fail_tmp =0



    def check_one_host(self,ipaddress):

        ipaddress=ipaddress
        self.result.append(ipaddress)

        myhost=new_host(self.count_dict,self.add_token,ipaddress,self.username,self.password,self.clusterid,self.audit,self.enabled,self.comment,self.serverport,self.rootpassword)
        if self.direct != "true":
            myhost.open()
            if myhost.result!="checked successfully":
                return [ipaddress,"check failed",myhost.result]
            else:
                return [ipaddress,"success"]
        else:
            return [ipaddress,"success"]


    def para_add(self):
        manager=Manager()
        self.count_dict=manager.dict()
        self.count_dict[self.check_ok]=0
        self.count_dict[self.check_fail]=0
        self.count_dict[self.check_remain]=len(self.ip_pool)
        pool=Pool(cpu_count())
        #start counting function
        t=threading.Thread(target=self.get_count,args=())
        t.start()
        #start ok
        check_result=pool.map(unwrap_self_check_one_host,self.new_pool)
        pool.close()
        pool.join()
        check_result.sort()
        for i in check_result:
            if i[1] == "success":
                try:
                    new_host = Hosts(i[0],self.username,self.password,self.rootpassword,self.serverport,self.clusterid,self.audit,self.enabled,self.comment)
                    db.session.add(new_host)
                    db.session.commit()
                    i.append("added successfully")
                except Exception as e:
                    db.session.rollback()
                    #log here later
                    i[1]="added failed"
                    if str(e).find("(IntegrityError)") != -1 :
                        i.append("already exist in database !")
                    else:
                        i.append("unknown error ! check the log")
                finally:
                    db.session.close()
        final_result=sorted(check_result)
        return final_result

    def __del__(self):
        self.count_dict.pop(self.check_remain)
        self.count_dict.pop(self.check_fail)
        self.count_dict.pop(self.check_ok)

def unwrap_self_check_one_host(mylist):
    """
    pool.map doesn't support class.function,thus ,add a outer function for multi_add.check_one_host
    """
    group=mylist[1]  #group is an object
    ipaddress=mylist[0]
    result=group.check_one_host(ipaddress)
    return result




if __name__ == '__main__':
    test_host=new_host('127.0.0.1','xiaosu','tdk')
    test_host.open()

        # try:
        #     myhost.insert_2_database()
        #     self.result.append("added successfully")
        # except Exception as e:
        #     if str(e).find("columns host_ip, login_name are not unique") != -1:
        #         self.result.append("already exist in database")
        #     else:
        #         #should log here
        #         print str(e)
        #         self.result.append("unknown error")
        # self.result.append(myhost.result)
        # return self.result

        # def insert_2_database(self):
        # new_host = Hosts(self.hostname,self.username,self.password,self.rootpassword,self.serverport,self.clusterid,self.audit,self.enabled,self.comment)
        # db.session.add(new_host)
        # db.session.commit()
        # # pass